package com.gzhryc.jdbc.dialect;

import com.gzhryc.jdbc.ConnectionFactory;
import com.gzhryc.jdbc.JdbcHelper;
import com.gzhryc.jdbc.JdbcSession;
import com.gzhryc.jdbc.Query;
import com.gzhryc.jdbc.annotaion.Column;
import com.gzhryc.jdbc.annotaion.Id;
import com.gzhryc.jdbc.log.Logger;
import com.gzhryc.jdbc.models.Parameters;
import com.gzhryc.jdbc.utils.EntityUtils;
import com.gzhryc.jdbc.utils.ResultSetUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;

/**
 * JDBC访问接口公共方法实现的抽象类
 *
 * @author 张浩
 * @version 1.0
 */
public abstract class AbsJdbcHelper implements JdbcHelper {

    static Logger log = Logger.getLogger(AbsJdbcHelper.class);

    /**
     * JDBC连接KEY
     */
    protected String connectionKey;

    public AbsJdbcHelper(String connectionKey) {
        this.connectionKey = connectionKey;
    }

    public String getJdbcKey() {
        return connectionKey;
    }

    /**
     * 获取数据库链接
     *
     * @return
     * @throws SQLException
     */
    protected Connection getConn() throws SQLException {
        Connection conn = JdbcSession.getConn(connectionKey);
        if (conn == null) {
            conn = ConnectionFactory.getConn(connectionKey, true);
        }
        return conn;
    }

    public <T> T get(String sql, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.read(pstmt.executeQuery(), clazz);
        } finally {
            // 如果是自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> T getJoin(String sql, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.readJoin(pstmt.executeQuery(), clazz);
        } finally {
            // 如果是自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> T get(Query query, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            String sql = query.getSql();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            query.getParams().toPreparedStatement(pstmt);
            log.sql(connectionKey,sql,query.getParams());
            return ResultSetUtils.read(pstmt.executeQuery(), clazz);
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> T getJoin(Query query, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            String sql = query.getSql();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            query.getParams().toPreparedStatement(pstmt);
            log.sql(connectionKey,sql,query.getParams());
            return ResultSetUtils.readJoin(pstmt.executeQuery(), clazz);
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public Map<String, Object> get(String sql) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.read(pstmt.executeQuery());
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public Map<String, Object> get(Query query) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            String sql = query.getSql();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            query.getParams().toPreparedStatement(pstmt);
            log.sql(connectionKey,sql,query.getParams());
            return ResultSetUtils.read(pstmt.executeQuery());
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    @Override
    public <T> T getFirstField(Query query, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            String sql = query.getSql();
            log.sql(connectionKey,sql,query.getParams());
            PreparedStatement pstmt = conn.prepareStatement(sql);
            query.getParams().toPreparedStatement(pstmt);
            Map<String, Object> data = ResultSetUtils.read(pstmt.executeQuery());
            if (data != null && data.size() > 0) {
                Object obj = data.values().iterator().next();
                if (obj != null) {
                    if (BigDecimal.class.isInstance(obj)) {
                        obj = BigDecimal.class.cast(obj).longValue();
                    }
                    return clazz.cast(obj);
                }
            }
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
        return null;
    }

    public <T> List<T> findFirstField(Query query, Class<T> clazz) throws SQLException {
        Connection conn = null;
        conn = getConn();
        String sql = query.getSql();
        if (query.isPaging()) {
            sql = sql + " limit " + query.getStartNum() + "," + query.getPageLimit();
        }
        PreparedStatement pstmt = conn.prepareStatement(sql);
        query.getParams().toPreparedStatement(pstmt);
        log.sql(connectionKey,sql,query.getParams());
        try {
            ArrayList<T> result = new ArrayList<>();
            List<Map<String, Object>> dataMap = ResultSetUtils.readList(pstmt.executeQuery());
            if (dataMap != null && dataMap.size() > 0) {
                for (Map<String, Object> map : dataMap) {
                    Object obj = map.values().iterator().next();
                    if (obj != null) {
                        if (obj instanceof BigDecimal) {
                            obj = ((BigDecimal) obj).longValue();
                        }
                        result.add(clazz.cast(obj));
                    }
                }

            }
            return result;
        } finally {
            if (pstmt != null) {
                pstmt.close();
            }
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> List<T> find(String sql, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.readList(pstmt.executeQuery(), clazz);
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> List<T> find(String sql, Parameters params, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            params.toPreparedStatement(pstmt);
            log.sql(connectionKey,sql,params);
            return ResultSetUtils.readList(pstmt.executeQuery(), clazz);
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> List<T> findJoin(String sql, Class<T> clazz) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.readJoinList(pstmt.executeQuery(), clazz);
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public List<Map<String, Object>> find(String sql) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return ResultSetUtils.readList(pstmt.executeQuery());
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public int update(String sql) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            log.sql(connectionKey,sql,null);
            return pstmt.executeUpdate();
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public int update(String sql, Parameters params) throws SQLException {
        Connection conn = null;
        try {
            conn = getConn();
            PreparedStatement pstmt = conn.prepareStatement(sql);
            params.toPreparedStatement(pstmt);
            log.sql(connectionKey,sql,params);
            return pstmt.executeUpdate();
        } finally {
            // 如果事自动事务则必需关闭链接
            if (conn != null && conn.getAutoCommit()) {
                conn.close();
            }
        }
    }

    public <T> int insert(T obj) throws SQLException {
        String tableName = EntityUtils.getTableName(obj.getClass());
        if (StringUtils.isNotBlank(tableName)) {
            return insert(tableName, obj);
        } else {
            throw new SQLException("Table Name is NULL");
        }
    }

    public <T> int insert(Class<T> clazz, List<T> data) throws SQLException {
        String tableName = EntityUtils.getTableName(clazz);
        if (StringUtils.isNotBlank(tableName)) {
            return insert(tableName, clazz, data);
        } else {
            throw new SQLException("Table Name is NULL");
        }
    }

    public <T> int insert(String tableName, Class<T> clazz, List<T> data) throws SQLException {
        try {
            Parameters params = new Parameters();
            Field idField = null;
            List<Field> fields = EntityUtils.findTableColumn(clazz);
            String sql = "INSERT INTO " + tableName;
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                Id id = field.getAnnotation(Id.class);
                if (id != null) {
                    idField = field;
                }
                if (id == null || !id.auto()) {
                    Column column = field.getAnnotation(Column.class);
                    if (column != null && !column.autoUpdateTime()) {
                        String name = EntityUtils.getName(field);
                        sb.append(",").append(name);
                    }
                } else if (id.auto()) {
                    if (idField.getType().equals(String.class)) {
                        String name = EntityUtils.getName(field);
                        sb.append(",").append(name);
                    }
                }
            }

            StringBuilder valueStrAll = new StringBuilder();
            for (T obj : data) {
                StringBuilder valueStr = new StringBuilder();
                for (int i = 0; i < fields.size(); i++) {
                    Field field = fields.get(i);
                    Id id = field.getAnnotation(Id.class);
                    if (id != null) {
                        idField = field;
                    }
                    if (id == null || !id.auto()) {
                        Column column = field.getAnnotation(Column.class);
                        if (column != null && !column.autoUpdateTime()) {
                            Object value = field.get(obj);
                            if (value != null) {
                                if (value instanceof String) {
                                    if (value.toString().contains("'") || value.toString().contains(";") || value.toString().contains("?")
                                            || value.toString().contains("#") || value.toString().contains("--") || value.toString().contains("/*")
                                            || value.toString().contains("\\") || value.toString().contains(",")) {
                                        valueStr.append(",?");
                                        params.setField(field, value);
                                    } else {
                                        valueStr.append(",'").append(value).append("'");
                                    }
                                } else if (Date.class.equals(field.getType())) {
                                    if (StringUtils.isNotBlank(column.type())) {
                                        if ("TIMESTAMP".equals(column.type().toUpperCase())) {
                                            valueStr.append(",").append(((Date) value).getTime());
                                        } else if ("TIMEDATE".equals(column.type().toUpperCase())) {
                                            valueStr.append(",'").append(DateFormatUtils.format((Date) value, "yyyy-MM-dd HH:mm:ss")).append("'");
                                        } else if ("TIME".equals(column.type().toUpperCase())) {
                                            valueStr.append(",'").append(DateFormatUtils.format((Date) value, "HH:mm:ss")).append("'");
                                        }
                                    } else {
                                        valueStr.append(",'").append(DateFormatUtils.format((Date) value, "yyyy-MM-dd HH:mm:ss")).append("'");
                                    }
                                } else {
                                    valueStr.append(",").append(value);
                                }
                            } else if(StringUtils.isNotBlank(column.defVal())) {
                                if (String.class.equals(field.getType())) {
                                    valueStr.append(",'").append(column.defVal()).append("'");
                                }else {
                                    valueStr.append(",").append(column.defVal());
                                }
                            }else {
                                valueStr.append(",NULL");
                            }
                        }
                    } else if (id.auto()) {
                        if (idField.getType().equals(String.class)) {
                            String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase();
                            valueStr.append(",'").append(uuid).append("'");
                        }
                    }
                }
                valueStrAll.append(",(").append(valueStr.substring(1)).append(")");
            }

            sql = sql + "(" + sb.substring(1) + ") VALUES " + valueStrAll.substring(1);
            return insert(sql, params);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new SQLException(e.fillInStackTrace());
        }
    }

    public <T> int insert(String tableName, T obj) throws SQLException {
        try {
            Field idField = null;
            Parameters params = new Parameters();
            List<Field> fields = EntityUtils.findTableColumn(obj.getClass());
            String sql = "insert into " + tableName;
            StringBuilder sbf = new StringBuilder();
            StringBuilder sbv = new StringBuilder();
            String uuid = null;
            for (int i = 0; i < fields.size(); i++) {
                Field field = fields.get(i);
                Id id = field.getAnnotation(Id.class);
                if (id != null) {
                    idField = field;
                }
                if (id == null || !id.auto()) {
                    Object value = field.get(obj);
                    if (value != null) {
                        String name = EntityUtils.getName(field);
                        sbf.append(",").append(name);
                        sbv.append(",?");
                        params.setField(field, value);
                    }
                } else if (id.auto()) {
                    if (idField.getType().equals(String.class)) {
                        uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase();
                        String name = EntityUtils.getName(field);
                        sbf.append(",").append(name);
                        sbv.append(",?");
                        params.setField(field, uuid);
                    }
                }
            }

            sql = sql + "(" + sbf.substring(1) + ")values(" + sbv.substring(1) + ")";
            int result = -1;
            if (idField != null) {
                Id id = idField.getAnnotation(Id.class);
                if (id.auto() && idField.getType().equals(Long.class)) {
                    Long idObj = insertByAutoId(sql, params);
                    if (idObj != null) {
                        idField.set(obj, idObj);
                    }
                    return 1;
                } else {
                    result = insert(sql, params);
                }
                // 成功后赋UUID的值
                if (result > 0 && id.auto() && idField.getType().equals(String.class)) {
                    idField.setAccessible(true);
                    idField.set(obj, uuid);
                }
            } else {
                result = insert(sql, params);
            }
            return result;
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw new SQLException(e.fillInStackTrace());
        }
    }
}
